package com.example.administrator.myapplication.net;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Dispatcher {
    private int maxRequests = 64;

    private int maxRequestsPreHosts = 5;

    private Deque<Call.AsyncCall> readyAsyncCalls = new ArrayDeque<>();
    private Deque<Call.AsyncCall> runingAsyncCalls = new ArrayDeque<>();

    private ExecutorService executorService;

    public synchronized ExecutorService executorService() {
        if (executorService == null) {
            ThreadFactory factory = new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "Http client");
                }
            };
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), factory);
        }
        return executorService;
    }

    public Dispatcher(int maxRequests, int maxRequestsPreHosts) {
        this.maxRequests = maxRequests;
        this.maxRequestsPreHosts = maxRequestsPreHosts;
    }

    public Dispatcher() {
    }

    public void enqueue(Call.AsyncCall call) {
        if (runingAsyncCalls.size() < maxRequests && runingCallsForHost(call) < maxRequestsPreHosts) {
            runingAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            readyAsyncCalls.add(call);
        }
    }

    private int runingCallsForHost(Call.AsyncCall call) {
        int result = 0;
        for (Call.AsyncCall c : runingAsyncCalls) {
            if (c.host().equals(call.host())) {
                result++;
            }
        }
        return result;
    }

    public void finish(Call.AsyncCall call) {
        synchronized (this) {
            runingAsyncCalls.remove(call);
            checkReady();
        }
    }

    private void checkReady() {
        if (runingAsyncCalls.size() >= maxRequests) {
            return;
        }
        if (readyAsyncCalls.isEmpty()) {
            return;
        }
        Iterator<Call.AsyncCall> iterator = readyAsyncCalls.iterator();
        while (iterator.hasNext()) {
            Call.AsyncCall next = iterator.next();
            if (runingCallsForHost(next) < maxRequestsPreHosts) {
                readyAsyncCalls.remove(next);
                runingAsyncCalls.add(next);
                executorService().execute(next);
            }
            if (runingAsyncCalls.size() > maxRequests) {
                return;
            }
        }
    }
}
